﻿using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Net;

namespace ILoli.Net
{
    public class Socket : System.Net.Sockets.Socket
    {
        ILoli.IO.IoCompletionPort _IoCompletionPort = null;

        /**********************************************************************
         * Constructors.
         * *******************************************************************/
        public Socket(SocketInformation SocketInformation)
            : base(SocketInformation)
        {
        }

        public Socket(AddressFamily AddressFamily, SocketType SocketType, ProtocolType ProtocolType)
            : base(AddressFamily, SocketType, ProtocolType)
        {
        }

        /**********************************************************************
         * Methods.
         * *******************************************************************/
        public ILoli.IO.IoCompletionPort AttachIoCompletionPort(ILoli.IO.IoCompletionPort IoCompletionPort, long CompletionKey)
        {
            if (_IoCompletionPort != null)
            {
                if (IoCompletionPort.AttachDevice(this, CompletionKey))
                {
                    _IoCompletionPort = IoCompletionPort;
                }
            }

            return _IoCompletionPort;
        }

        public void DetachIoCompletionPort()
        {
            try
            {
                _IoCompletionPort.DetachDevice(this);
                _IoCompletionPort = null;
            }
            catch (System.NullReferenceException)
            {
            	
            }
        }

        public void Accept(ILoli.IO.Overlapped Overlapped)
        {
            if (_IoCompletionPort == null)
            {
                throw new ILoli.Net.SocketException(SocketExceptionCode.SOCKET_NOT_ATTACHE_TO_IOCOMPLETIONPORT);
            }

            Overlapped.InternalHandle = this;
            Overlapped.Offset = 0;
            BeginAccept(AcceptCallback, Overlapped);
        }

        public void Accept(int ReceiveSize, ILoli.IO.Overlapped Overlapped)
        {
            if (_IoCompletionPort == null)
            {
                throw new ILoli.Net.SocketException(SocketExceptionCode.SOCKET_NOT_ATTACHE_TO_IOCOMPLETIONPORT);
            }
            Overlapped.InternalHandle = this;
            Overlapped.Offset = ReceiveSize;
            BeginAccept(ReceiveSize, AcceptCallback, Overlapped);
        }

        public void Accept(System.Net.Sockets.Socket AcceptSocket, int ReceiveSize, ILoli.IO.Overlapped Overlapped)
        {
            if (_IoCompletionPort == null)
            {
                throw new ILoli.Net.SocketException(SocketExceptionCode.SOCKET_NOT_ATTACHE_TO_IOCOMPLETIONPORT);
            }
            Overlapped.InternalHandle = this;
            Overlapped.Offset = ReceiveSize;
            BeginAccept(AcceptSocket, ReceiveSize, AcceptCallback, Overlapped);
        }

        public void Connect(EndPoint EndPoint, ILoli.IO.Overlapped Overlapped)
        {
            if (_IoCompletionPort == null)
            {
                throw new ILoli.Net.SocketException(SocketExceptionCode.SOCKET_NOT_ATTACHE_TO_IOCOMPLETIONPORT);
            }

            Overlapped.InternalHandle = this;
            BeginConnect(EndPoint, ConnectCallback, Overlapped);
        }

        public void Connect(IPAddress IPAddress, int Port, ILoli.IO.Overlapped Overlapped)
        {
            if (_IoCompletionPort == null)
            {
                throw new ILoli.Net.SocketException(SocketExceptionCode.SOCKET_NOT_ATTACHE_TO_IOCOMPLETIONPORT);
            }

            Overlapped.InternalHandle = this;
            BeginConnect(IPAddress, Port, ConnectCallback, Overlapped);
        }

        public void Connect(IPAddress[] IPAddress, int Port, ILoli.IO.Overlapped Overlapped)
        {
            if (_IoCompletionPort == null)
            {
                throw new ILoli.Net.SocketException(SocketExceptionCode.SOCKET_NOT_ATTACHE_TO_IOCOMPLETIONPORT);
            }

            Overlapped.InternalHandle = this;
            BeginConnect(IPAddress, Port, ConnectCallback, Overlapped);
        }

        public void Connect(String Ip, int Port, ILoli.IO.Overlapped Overlapped)
        {
            if (_IoCompletionPort == null)
            {
                throw new ILoli.Net.SocketException(SocketExceptionCode.SOCKET_NOT_ATTACHE_TO_IOCOMPLETIONPORT);
            }

            Overlapped.InternalHandle = this;
            BeginConnect(Ip, Port, ConnectCallback, Overlapped);
        }

        /**********************************************************************
         * Attributes.
         * *******************************************************************/
        public ILoli.IO.IoCompletionPort IoCompletionPort
        {
            get
            {
                return _IoCompletionPort;
            }
        }

        /**********************************************************************
         * Callbacks.
         * *******************************************************************/
        static void AcceptCallback(IAsyncResult ar)
        {
            ILoli.IO.Overlapped overlapped = (ILoli.IO.Overlapped)ar.AsyncState;
            ILoli.Net.Socket socket = (ILoli.Net.Socket)overlapped.InternalHandle;

            try
            {
                if (overlapped.Offset != 0)
                {
                    overlapped.InternalHandle = socket.EndAccept(out overlapped.Buffer, out overlapped.NumberOfBytesTransferred, ar);
                }
                else
                {
                    overlapped.InternalHandle = socket.EndAccept(ar);
                }
            }
            catch (System.Exception ex)
            {
                overlapped.UserHandle = ex;
            }

            socket.IoCompletionPort.PostQueuedCompletionStatus(socket, overlapped.NumberOfBytesTransferred, overlapped);
        }

        static void ConnectCallback(IAsyncResult ar)
        {
            ILoli.IO.Overlapped overlapped = (ILoli.IO.Overlapped)ar.AsyncState;
            ILoli.Net.Socket socket = (ILoli.Net.Socket)overlapped.InternalHandle;

            try
            {
                socket.EndConnect(ar);
            }
            catch (System.Exception ex)
            {
                overlapped.UserHandle = ex;
            }

            socket.IoCompletionPort.PostQueuedCompletionStatus(socket, 0, overlapped);
        }

    }
}
